/*
 * Copyright (c) 2005,2006,2007 INRIA
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
 */

#include "ns3/command-line.h"
#include "ns3/config.h"
#include "ns3/gnuplot.h"
#include "ns3/ipv4-address-helper.h"
#include "ns3/log.h"
#include "ns3/mobility-helper.h"
#include "ns3/mobility-model.h"
#include "ns3/on-off-helper.h"
#include "ns3/packet-socket-address.h"
#include "ns3/packet-socket-helper.h"
#include "ns3/string.h"
#include "ns3/uinteger.h"
#include "ns3/yans-wifi-channel.h"
#include "ns3/yans-wifi-helper.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE("Wifi-Adhoc");

/**
 * WiFi adhoc experiment class.
 *
 * It handles the creation and run of an experiment.
 */
class Experiment
{
  public:
    Experiment();
    /**
     * Constructor.
     * @param name The name of the experiment.
     */
    Experiment(std::string name);

    /**
     * Run an experiment.
     * @param wifi      //!< The WifiHelper class.
     * @param wifiPhy   //!< The YansWifiPhyHelper class.
     * @param wifiMac   //!< The WifiMacHelper class.
     * @param wifiChannel //!< The YansWifiChannelHelper class.
     * @return a 2D dataset of the experiment data.
     */
    Gnuplot2dDataset Run(const WifiHelper& wifi,
                         const YansWifiPhyHelper& wifiPhy,
                         const WifiMacHelper& wifiMac,
                         const YansWifiChannelHelper& wifiChannel);

  private:
    /**
     * Receive a packet.
     * @param socket The receiving socket.
     */
    void ReceivePacket(Ptr<Socket> socket);
    /**
     * Set the position of a node.
     * @param node The node.
     * @param position The position of the node.
     */
    void SetPosition(Ptr<Node> node, Vector position);
    /**
     * Get the position of a node.
     * @param node The node.
     * @return the position of the node.
     */
    Vector GetPosition(Ptr<Node> node);
    /**
     * Move a node by 1m on the x axis, stops at 210m.
     * @param node The node.
     */
    void AdvancePosition(Ptr<Node> node);
    /**
     * Setup the receiving socket.
     * @param node The receiving node.
     * @return the socket.
     */
    Ptr<Socket> SetupPacketReceive(Ptr<Node> node);

    uint32_t m_bytesTotal;     //!< The number of received bytes.
    Gnuplot2dDataset m_output; //!< The output dataset.
};

Experiment::Experiment()
{
}

Experiment::Experiment(std::string name)
    : m_output(name)
{
    m_output.SetStyle(Gnuplot2dDataset::LINES);
}

void
Experiment::SetPosition(Ptr<Node> node, Vector position)
{
    Ptr<MobilityModel> mobility = node->GetObject<MobilityModel>();
    mobility->SetPosition(position);
}

Vector
Experiment::GetPosition(Ptr<Node> node)
{
    Ptr<MobilityModel> mobility = node->GetObject<MobilityModel>();
    return mobility->GetPosition();
}

void
Experiment::AdvancePosition(Ptr<Node> node)
{
    Vector pos = GetPosition(node);
    double mbs = ((m_bytesTotal * 8.0) / 1000000);
    m_bytesTotal = 0;
    m_output.Add(pos.x, mbs);
    pos.x += 1.0;
    if (pos.x >= 210.0)
    {
        return;
    }
    SetPosition(node, pos);
    Simulator::Schedule(Seconds(1), &Experiment::AdvancePosition, this, node);
}

void
Experiment::ReceivePacket(Ptr<Socket> socket)
{
    Ptr<Packet> packet;
    while ((packet = socket->Recv()))
    {
        m_bytesTotal += packet->GetSize();
    }
}

Ptr<Socket>
Experiment::SetupPacketReceive(Ptr<Node> node)
{
    TypeId tid = TypeId::LookupByName("ns3::PacketSocketFactory");
    Ptr<Socket> sink = Socket::CreateSocket(node, tid);
    sink->Bind();
    sink->SetRecvCallback(MakeCallback(&Experiment::ReceivePacket, this));
    return sink;
}

Gnuplot2dDataset
Experiment::Run(const WifiHelper& wifi,
                const YansWifiPhyHelper& wifiPhy,
                const WifiMacHelper& wifiMac,
                const YansWifiChannelHelper& wifiChannel)
{
    m_bytesTotal = 0;

    NodeContainer c;
    c.Create(2);

    PacketSocketHelper packetSocket;
    packetSocket.Install(c);

    YansWifiPhyHelper phy = wifiPhy;
    phy.SetChannel(wifiChannel.Create());

    NetDeviceContainer devices = wifi.Install(phy, wifiMac, c);

    MobilityHelper mobility;
    Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator>();
    positionAlloc->Add(Vector(0.0, 0.0, 0.0));
    positionAlloc->Add(Vector(5.0, 0.0, 0.0));
    mobility.SetPositionAllocator(positionAlloc);
    mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");

    mobility.Install(c);

    PacketSocketAddress socket;
    socket.SetSingleDevice(devices.Get(0)->GetIfIndex());
    socket.SetPhysicalAddress(devices.Get(1)->GetAddress());
    socket.SetProtocol(1);

    OnOffHelper onoff("ns3::PacketSocketFactory", Address(socket));
    onoff.SetConstantRate(DataRate(60000000));
    onoff.SetAttribute("PacketSize", UintegerValue(2000));

    ApplicationContainer apps = onoff.Install(c.Get(0));
    apps.Start(Seconds(0.5));
    apps.Stop(Seconds(250));

    Simulator::Schedule(Seconds(1.5), &Experiment::AdvancePosition, this, c.Get(1));
    Ptr<Socket> recvSink = SetupPacketReceive(c.Get(1));

    Simulator::Run();

    Simulator::Destroy();

    return m_output;
}

int
main(int argc, char* argv[])
{
    CommandLine cmd(__FILE__);
    cmd.Parse(argc, argv);

    Gnuplot gnuplot = Gnuplot("reference-rates.png");

    Experiment experiment;
    WifiHelper wifi;
    wifi.SetStandard(WIFI_STANDARD_80211a);
    WifiMacHelper wifiMac;
    YansWifiPhyHelper wifiPhy;
    YansWifiChannelHelper wifiChannel = YansWifiChannelHelper::Default();
    Gnuplot2dDataset dataset;

    wifiMac.SetType("ns3::AdhocWifiMac");

    NS_LOG_DEBUG("54");
    experiment = Experiment("54mb");
    wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
                                 "DataMode",
                                 StringValue("OfdmRate54Mbps"));
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("48");
    experiment = Experiment("48mb");
    wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
                                 "DataMode",
                                 StringValue("OfdmRate48Mbps"));
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("36");
    experiment = Experiment("36mb");
    wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
                                 "DataMode",
                                 StringValue("OfdmRate36Mbps"));
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("24");
    experiment = Experiment("24mb");
    wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
                                 "DataMode",
                                 StringValue("OfdmRate24Mbps"));
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("18");
    experiment = Experiment("18mb");
    wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
                                 "DataMode",
                                 StringValue("OfdmRate18Mbps"));
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("12");
    experiment = Experiment("12mb");
    wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
                                 "DataMode",
                                 StringValue("OfdmRate12Mbps"));
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("9");
    experiment = Experiment("9mb");
    wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
                                 "DataMode",
                                 StringValue("OfdmRate9Mbps"));
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("6");
    experiment = Experiment("6mb");
    wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager",
                                 "DataMode",
                                 StringValue("OfdmRate6Mbps"));
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    gnuplot.GenerateOutput(std::cout);

    gnuplot = Gnuplot("rate-control.png");

    NS_LOG_DEBUG("arf");
    experiment = Experiment("arf");
    wifi.SetRemoteStationManager("ns3::ArfWifiManager");
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("aarf");
    experiment = Experiment("aarf");
    wifi.SetRemoteStationManager("ns3::AarfWifiManager");
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("aarf-cd");
    experiment = Experiment("aarf-cd");
    wifi.SetRemoteStationManager("ns3::AarfcdWifiManager");
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("cara");
    experiment = Experiment("cara");
    wifi.SetRemoteStationManager("ns3::CaraWifiManager");
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("rraa");
    experiment = Experiment("rraa");
    wifi.SetRemoteStationManager("ns3::RraaWifiManager");
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    NS_LOG_DEBUG("ideal");
    experiment = Experiment("ideal");
    wifi.SetRemoteStationManager("ns3::IdealWifiManager");
    dataset = experiment.Run(wifi, wifiPhy, wifiMac, wifiChannel);
    gnuplot.AddDataset(dataset);

    gnuplot.GenerateOutput(std::cout);

    return 0;
}
